Completed
Push — master ( d25395...d83c14 )
by Jan
15s queued 14s
created

ID3Util.ts ➔ splitNullTerminatedBuffer   B

Complexity

Conditions 6

Size

Total Lines 28
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 28
rs 8.3946
c 0
b 0
f 0
cc 6
1
import iconv = require('iconv-lite')
2
import { FrameOptions, FRAME_OPTIONS } from './definitions/FrameOptions'
3
import { isKeyOf, isString } from './util'
4
5
export class SplitBuffer {
6
    value: Buffer | null
7
    remainder: Buffer | null
8
    constructor(value: Buffer | null = null, remainder: Buffer | null = null) {
9
        this.value = value
10
        this.remainder = remainder
11
    }
12
}
13
14
export function splitNullTerminatedBuffer(buffer: Buffer, encodingByte = 0x00) {
15
    const termination = { start: -1, size: 0 }
16
    if (encodingByte === 0x01 || encodingByte === 0x02) {
17
        termination.start = buffer.indexOf(Buffer.from([0x00, 0x00]))
18
        termination.size = 2
19
        if (
20
            termination.start !== -1 &&
21
            buffer.length > (termination.start + termination.size)
22
        ) {
23
            if (buffer[termination.start + termination.size] === 0x00) {
24
                termination.start += 1
25
            }
26
        }
27
    } else {
28
        termination.start = buffer.indexOf(0x00)
29
        termination.size = 1
30
    }
31
32
    if (termination.start === -1) {
33
        return new SplitBuffer(null, buffer.subarray(0))
34
    }
35
    if (buffer.length <= termination.start + termination.size) {
36
        return new SplitBuffer(buffer.subarray(0, termination.start), null)
37
    }
38
    return new SplitBuffer(
39
        buffer.subarray(0, termination.start),
40
        buffer.subarray(termination.start + termination.size)
41
    )
42
}
43
44
export function encodingFromStringOrByte(encoding: string | number) {
45
    const ENCODINGS = [
46
        'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
47
    ]
48
49
    if (isString(encoding) && ENCODINGS.includes(encoding)) {
50
        return encoding
51
    }
52
    if (
53
        typeof encoding === "number" &&
54
        encoding >= 0 && encoding < ENCODINGS.length
55
    ) {
56
        return ENCODINGS[encoding]
57
    }
58
    return ENCODINGS[0]
59
}
60
61
export function stringToEncodedBuffer(
62
    value: string,
63
    encodingByte: string | number
64
) {
65
    return iconv.encode(
66
        value,
67
        encodingFromStringOrByte(encodingByte)
68
    )
69
}
70
71
export function bufferToDecodedString(
72
    buffer: Buffer,
73
    encodingByte: string | number
74
) {
75
    return iconv.decode(
76
        buffer,
77
        encodingFromStringOrByte(encodingByte)
78
    ).replace(/\0/g, '')
79
}
80
81
export function getSpecOptions(frameIdentifier: string): FrameOptions {
82
    if (isKeyOf(frameIdentifier, FRAME_OPTIONS)) {
83
        return FRAME_OPTIONS[frameIdentifier]
84
    }
85
    return {
86
        multiple: false
87
    }
88
}
89
90
export function isValidID3Header(buffer: Buffer) {
91
    if (buffer.length < 10) {
92
        return false
93
    }
94
    if (buffer.readUIntBE(0, 3) !== 0x494433) {
95
        return false
96
    }
97
    if ([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
98
        return false
99
    }
100
    return isValidEncodedSize(buffer.subarray(6, 10))
101
}
102
103
/**
104
 * Returns -1 if no tag was found.
105
 */
106
export function getTagPosition(buffer: Buffer) {
107
    // Search Buffer for valid ID3 frame
108
    const tagHeaderSize = 10
109
    let position = -1
110
    let headerValid = false
111
    do {
112
        position = buffer.indexOf("ID3", position + 1)
113
        if (position !== -1) {
114
            // It's possible that there is a "ID3" sequence without being an
115
            // ID3 Frame, so we need to check for validity of the next 10 bytes.
116
            headerValid = isValidID3Header(
117
                buffer.subarray(position, position + tagHeaderSize)
118
            )
119
        }
120
    } while (position !== -1 && !headerValid)
121
122
    if (!headerValid) {
123
        return -1
124
    }
125
    return position
126
}
127
128
 export function isValidEncodedSize(encodedSize: Buffer) {
129
    // The size must not have the bit 7 set
130
    return ((
131
        encodedSize[0] |
132
        encodedSize[1] |
133
        encodedSize[2] |
134
        encodedSize[3]
135
    ) & 128) === 0
136
}
137
138
/**
139
 * ID3 header size uses only 7 bits of a byte, bit shift is needed.
140
 * @returns Return a Buffer of 4 bytes with the encoded size
141
 */
142
 export function encodeSize(size: number) {
143
    const byte_3 = size & 0x7F
144
    const byte_2 = (size >> 7) & 0x7F
145
    const byte_1 = (size >> 14) & 0x7F
146
    const byte_0 = (size >> 21) & 0x7F
147
    return Buffer.from([byte_0, byte_1, byte_2, byte_3])
148
}
149
150
/**
151
 * Decode the encoded size from an ID3 header.
152
 */
153
 export function decodeSize(encodedSize: Buffer) {
154
    return (
155
        (encodedSize[0] << 21) +
156
        (encodedSize[1] << 14) +
157
        (encodedSize[2] << 7) +
158
        encodedSize[3]
159
    )
160
}
161
162
export function getFrameSize(buffer: Buffer, decode: boolean, version: number) {
163
    const decodeBytes = version > 2 ?
164
        [buffer[4], buffer[5], buffer[6], buffer[7]] :
165
        [buffer[3], buffer[4], buffer[5]]
166
    if (decode) {
167
        return decodeSize(Buffer.from(decodeBytes))
168
    }
169
    return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
170
}
171
172
export function parseTagHeaderFlags(header: Buffer) {
173
    if (!(header instanceof Buffer && header.length >= 10)) {
174
        return {}
175
    }
176
    const version = header[3]
177
    const flagsByte = header[5]
178
    if (version === 3) {
179
        return {
180
            unsynchronisation: !!(flagsByte & 128),
181
            extendedHeader: !!(flagsByte & 64),
182
            experimentalIndicator: !!(flagsByte & 32)
183
        }
184
    }
185
    if (version === 4) {
186
        return {
187
            unsynchronisation: !!(flagsByte & 128),
188
            extendedHeader: !!(flagsByte & 64),
189
            experimentalIndicator: !!(flagsByte & 32),
190
            footerPresent: !!(flagsByte & 16)
191
        }
192
    }
193
    return {}
194
}
195
196
export function processUnsynchronisedBuffer(buffer: Buffer) {
197
    const newDataArr = []
198
    if (buffer.length > 0) {
199
        newDataArr.push(buffer[0])
200
    }
201
    for(let i = 1; i < buffer.length; i++) {
202
        if (buffer[i - 1] === 0xFF && buffer[i] === 0x00) {
203
            continue
204
        }
205
        newDataArr.push(buffer[i])
206
    }
207
    return Buffer.from(newDataArr)
208
}
209
210
export function getPictureMimeTypeFromBuffer(pictureBuffer: Buffer) {
211
    if (
212
        pictureBuffer.length > 3 &&
213
        pictureBuffer.compare(Buffer.from([0xff, 0xd8, 0xff]), 0, 3, 0, 3) === 0
214
    ) {
215
        return "image/jpeg"
216
    }
217
    if (
218
        pictureBuffer.length > 8 &&
219
        pictureBuffer.compare(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), 0, 8, 0, 8) === 0
220
    ) {
221
        return "image/png"
222
    }
223
    return null
224
}
225